home *** CD-ROM | disk | FTP | other *** search
- <?xml version="1.0"?>
- <xsl:stylesheet version="1.0"
- xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
- xmlns:svg="http://www.w3.org/2000/svg">
- <xsl:output method="xml" indent="yes"/>
- <!-- global variable for bar width -->
- <xsl:variable name="bar_width" select="number(80)"/>
- <!-- spacing between bars -->
- <xsl:variable name="bar_spacing" select="number(10)"/>
- <!-- graph height scaling -->
- <xsl:variable name="graph_height" select="number(500)"/>
- <xsl:variable name="left_margin" select="number(100)"/>
-
- <!-- key used for distinct product ids -->
- <xsl:key name="kDistinctProdIDs" match="product_sales" use="@product_id"/>
- <!-- key for each prod id within week -->
- <xsl:key name="kWeeksProd" match="/sales_summary/weekly_sales/product_sales" use="concat(../@week_no,'||',@product_id)"/>
-
- <xsl:variable name="DistinctProducts" select="/sales_summary/weekly_sales/product_sales[generate-id() = generate-id(key('kDistinctProdIDs',@product_id))]"/>
-
- <xsl:template match="/">
- <!-- body root of svg -->
- <svg:svg id="body" viewBox="0 0 -100 {$graph_height + 120}">
- <!-- script for showing/hiding actual value hints on bars -->
- <script type="text/ecmascript"><![CDATA[
- function DoOnOver(evt,HintId){
- // get document...
- var target = evt.getTarget();
- var doc = target.getOwnerDocument();
- // get the hint graphic...
- var Hint = doc.getElementById(HintId);
- // make hint graphic visible...
- Hint.setAttribute('style', 'visibility:visible');
- }
- function DoOnOut(evt,HintId){
- // get document...
- var target = evt.getTarget();
- var doc = target.getOwnerDocument();
- // get the hint graphic...
- var Hint = doc.getElementById(HintId);
- // make hint graphic visible...
- Hint.setAttribute('style', 'visibility:hidden');
- }
- ]]></script>
- <!-- graphic title -->
- <svg:title>Weekly Sales Values</svg:title>
- <!-- main graphic -->
- <svg:g id="barChart" transform="translate(10, 10)" fill-rule="nonzero" clip-rule="nonzero" stroke="none" class="legend"
- stroke-width="1" stroke-linecap="square" stroke-miterlimit="1" style="text-anchor:start" shape-rendering="crispEdges">
- <!-- apply data -->
- <xsl:apply-templates select="sales_summary"/>
- <svg:text transform="matrix(2 0 0 2 {$left_margin} {$graph_height + 70})">Weekly Total Sales Values</svg:text>
- </svg:g>
- </svg:svg>
- </xsl:template>
-
- <!-- main data template -->
- <xsl:template match="sales_summary">
- <!-- calculate the number of weeks -->
- <xsl:variable name="weeks_count" select="count(weekly_sales)"/>
- <!-- calculate the maximum weekly sales value -->
- <xsl:variable name="max_week_sales_value">
- <xsl:for-each select="weekly_sales">
- <xsl:sort select="sum(product_sales/@value)" data-type="number" order="descending"/>
- <xsl:if test="position() = 1">
- <xsl:value-of select="sum(product_sales/@value)"/>
- </xsl:if>
- </xsl:for-each>
- </xsl:variable>
- <!-- max graph height is max sales rounded to next 100 -->
- <xsl:variable name="max_graph_height" select="floor(($max_week_sales_value + 99) div 100) * 100"/>
- <!-- draw graph background -->
- <xsl:call-template name="draw_graph">
- <xsl:with-param name="max_graph_height" select="$max_graph_height"/>
- <xsl:with-param name="bar_count" select="$weeks_count"/>
- </xsl:call-template>
- <!-- draw the graph bars themselves from data -->
- <xsl:apply-templates select="weekly_sales">
- <xsl:sort select="@week_no" data-type="number"/>
- <xsl:with-param name="max_graph_height" select="$max_graph_height"/>
- <xsl:with-param name="bar_count" select="$weeks_count"/>
- </xsl:apply-templates>
- </xsl:template>
-
- <!-- draw graph background and vertical scale/legends -->
- <xsl:template name="draw_graph">
- <xsl:param name="max_graph_height"/>
- <xsl:param name="bar_count"/>
- <xsl:variable name="actual_width" select="($bar_count * ($bar_width + $bar_spacing)) + $bar_spacing"/>
- <svg:g id="GridAndLegend" style="stroke:none;" shape-rendering="crispEdges" stroke-width="1">
- <!-- back face and surrounding lines -->
- <svg:path fill="lightgray" stroke="black" d="M {$left_margin},{$graph_height + 20} h{$actual_width} v-{$graph_height} h-{$actual_width} v{$graph_height}"/>
- <!-- draw zero mark and legend -->
- <svg:path fill="none" stroke="black" d="M {$left_margin - 10},{$graph_height + 20} h10"/>
- <svg:text text-anchor="end" baseline-shift="-3" transform="matrix(1 0 0 1 {$left_margin - 15} {$graph_height + 20})">0</svg:text>
- <!-- draw vertical lines and legends -->
- <xsl:call-template name="draw_graph_vertical_legends">
- <xsl:with-param name="max" select="$max_graph_height"/>
- <xsl:with-param name="legend_threshold" select="$max_graph_height div $graph_height"/>
- <xsl:with-param name="actual_width" select="$actual_width"/>
- </xsl:call-template>
- <!-- draw color box legends -->
- <xsl:for-each select="$DistinctProducts">
- <xsl:variable name="calculated_color">
- <xsl:call-template name="calc_color">
- <xsl:with-param name="rel_position" select="position()"/>
- </xsl:call-template>
- </xsl:variable>
- <svg:path fill="{$calculated_color}" stroke="black" d="M {$left_margin + ((position() - 1) * 100)},{$graph_height + 90} h10 v-10 h-10 v10"/>
- <svg:text transform="matrix(1.1 0 0 1.1 {$left_margin + ((position() - 1) * 100) + 13} {$graph_height + 90})">
- <xsl:value-of select="@product_id"/>
- </svg:text>
- </xsl:for-each>
- </svg:g>
- </xsl:template>
-
- <!-- recursive template to draw vertical lines and legends -->
- <xsl:template name="draw_graph_vertical_legends">
- <xsl:param name="max"/>
- <xsl:param name="legend_threshold"/>
- <xsl:param name="actual_width"/>
- <!-- params used only during recursion -->
- <xsl:param name="start" select="number(100)"/>
- <xsl:param name="step" select="number(100)"/>
- <xsl:param name="prev_marked_start" select="number(0)"/>
- <!-- calculate actual vertical 'pixel' position for this mark and previous drawn mark -->
- <xsl:variable name="prev_vert_posn" select="($prev_marked_start div $max) * $graph_height"/>
- <xsl:variable name="vert_posn" select="($start div $max) * $graph_height"/>
- <!-- work out whether this line is too close to previous line -->
- <xsl:variable name="new_marked_start">
- <xsl:choose>
- <xsl:when test="($vert_posn - $prev_vert_posn) >= $legend_threshold">
- <xsl:value-of select="$start"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="$prev_marked_start"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <!-- only draw line and legend when not too close -->
- <xsl:if test="$new_marked_start = $start">
- <svg:path fill="none" stroke="black" d="M {$left_margin - 10},{$graph_height + 20 - floor($vert_posn)} h{$actual_width + 10}"/>
- <svg:text text-anchor="end" baseline-shift="-3" transform="matrix(1 0 0 1 {$left_margin - 15} {$graph_height + 20 - floor($vert_posn)})">
- <xsl:value-of select="$start"/>
- </svg:text>
- </xsl:if>
- <!-- if not yet reached max then recurse call to this template -->
- <xsl:if test="$start < $max">
- <xsl:call-template name="draw_graph_vertical_legends">
- <xsl:with-param name="max" select="$max"/>
- <xsl:with-param name="legend_threshold" select="$legend_threshold"/>
- <xsl:with-param name="actual_width" select="$actual_width"/>
- <xsl:with-param name="start" select="$start + $step"/>
- <xsl:with-param name="step" select="$step"/>
- <xsl:with-param name="prev_marked_start" select="$new_marked_start"/>
- </xsl:call-template>
- </xsl:if>
- </xsl:template>
-
- <!-- applied template that handles each week -->
- <!-- then calls routine for drawing stacked bars -->
- <xsl:template match="weekly_sales">
- <xsl:param name="max_graph_height"/>
- <xsl:param name="bar_count"/>
- <!-- calc left position of this bar -->
- <xsl:variable name="bar_left" select="((position() - 1) * ($bar_width + $bar_spacing)) + $bar_spacing"/>
- <!-- draw the bar -->
- <svg:g id="weekbar_{@week_no}" onmouseover="DoOnOver(evt,'Hint_{@week_no}')" onmouseout="DoOnOut(evt,'Hint_{@week_no}')">
- <xsl:call-template name="draw_bar_stacks">
- <xsl:with-param name="week_no" select="@week_no"/>
- <xsl:with-param name="max_graph_height" select="$max_graph_height"/>
- <xsl:with-param name="bar_left" select="$bar_left"/>
- </xsl:call-template>
- </svg:g>
- <!-- draw the legend -->
- <svg:text text-anchor="middle" transform="matrix(1.5 0 0 1.5 {$left_margin + $bar_left + ($bar_width div 2)} {$graph_height + 40})">
- <xsl:text>Week </xsl:text>
- <xsl:value-of select="@week_no"/>
- </svg:text>
- <!-- create the invisible hint info for the bar -->
- <svg:g id="Hint_{@week_no}" style="visibility: hidden;" transform="matrix(1 0 0 1 {$left_margin + $bar_spacing + ($bar_count * ($bar_width + $bar_spacing)) + 20} 20)">
- <!-- draw box around hint -->
- <xsl:variable name="box_height" select="(count(product_sales) * 20) + 50"/>
- <svg:path fill="none" stroke="black" d="M 0,0 h160 v{$box_height} h-160 v-{$box_height}"/>
- <!-- draw centered week no at top -->
- <svg:text text-anchor="middle" transform="matrix(1.5 0 0 1.5 80 20)">
- <xsl:text>Week </xsl:text>
- <xsl:value-of select="@week_no"/>
- </svg:text>
- <!-- draw legends and figures for breakdown of this week -->
- <xsl:variable name="this_week" select="@week_no"/>
- <xsl:for-each select="$DistinctProducts">
- <xsl:variable name="calculated_color">
- <xsl:call-template name="calc_color">
- <xsl:with-param name="rel_position" select="position()"/>
- </xsl:call-template>
- </xsl:variable>
- <svg:path fill="{$calculated_color}" stroke="black" d="M 10,{(position() * 20) + 20} h10 v-10 h-10 v10"/>
- <svg:text transform="matrix(1.1 0 0 1.1 25 {(position() * 20) + 20})">
- <xsl:value-of select="@product_id"/>
- </svg:text>
- <svg:text text-anchor="end" transform="matrix(1.1 0 0 1.1 150 {(position() * 20) + 20})">
- <xsl:choose>
- <xsl:when test="key('kWeeksProd',concat($this_week,'||',@product_id))">
- <xsl:value-of select="format-number(key('kWeeksProd',concat($this_week,'||',@product_id))/@value,'$#,##0')"/>
- </xsl:when>
- <xsl:otherwise>(none)</xsl:otherwise>
- </xsl:choose>
- </svg:text>
- </xsl:for-each>
- <!-- week total -->
- <svg:text text-anchor="end" transform="matrix(1.1 0 0 1.1 150 {$box_height - 10})">
- <xsl:text>Total </xsl:text>
- <xsl:value-of select="format-number(sum(product_sales/@value),'$#,##0')"/>
- </svg:text>
-
- <!--
- <svg:path fill="none" stroke="red" d="M {$left_margin - 10},{$graph_height + 20 - $bar_height} h{$bar_left + 10}"/>
- <svg:text transform="matrix(1.5 0 0 1.5 {$left_margin + $bar_left + 2} {($graph_height + 35) - $bar_height})">
- <xsl:value-of select="format-number($sales_value,'#,##0')"/>
- </svg:text>
- -->
- </svg:g>
- </xsl:template>
-
- <!-- recursive template for drawing the stacked parts of each bar -->
- <!-- a recursive template is used because of need to track the -->
- <!-- the height of previous bar parts on drawing each new part. -->
- <!-- Also, the bar position for each part needs to be consistent -->
- <!-- so that the bar color for each product is consistent. -->
- <xsl:template name="draw_bar_stacks">
- <xsl:param name="week_no"/>
- <xsl:param name="max_graph_height"/>
- <xsl:param name="bar_left"/>
- <!-- params used only in recursion -->
- <xsl:param name="on_product" select="number(1)"/>
- <xsl:param name="prev_height" select="number(0)"/>
- <!-- get the item for this part of the bar -->
- <xsl:variable name="product_id" select="$DistinctProducts[$on_product]/@product_id"/>
- <xsl:variable name="this_weeks_product" select="key('kWeeksProd',concat($week_no,'||',$product_id))"/>
- <!-- calc the height of this bar part -->
- <xsl:variable name="bar_height">
- <xsl:choose>
- <xsl:when test="$this_weeks_product">
- <xsl:value-of select="floor(($this_weeks_product/@value div $max_graph_height) * $graph_height)"/>
- </xsl:when>
- <xsl:otherwise>0</xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <!-- only draw this part of stack if the product exists in this week -->
- <xsl:if test="$this_weeks_product">
- <!-- calc the color -->
- <xsl:variable name="bar_color">
- <xsl:call-template name="calc_color">
- <xsl:with-param name="rel_position" select="$on_product"/>
- </xsl:call-template>
- </xsl:variable>
- <svg:path fill="{$bar_color}" stroke="none" d="M {$bar_left + $left_margin},{$graph_height + 19 - $prev_height} h{$bar_width} v-{$bar_height} h-{$bar_width} v{$bar_height}"/>
- </xsl:if>
- <!-- do next product -->
- <xsl:if test="$DistinctProducts[$on_product + 1]">
- <xsl:call-template name="draw_bar_stacks">
- <xsl:with-param name="week_no" select="$week_no"/>
- <xsl:with-param name="max_graph_height" select="$max_graph_height"/>
- <xsl:with-param name="bar_left" select="$bar_left"/>
- <xsl:with-param name="on_product" select="$on_product + 1"/>
- <xsl:with-param name="prev_height" select="$prev_height + $bar_height"/>
- </xsl:call-template>
- </xsl:if>
- </xsl:template>
-
- <!-- called template for calculating colors -->
- <!-- this is a crude way of giving each bar a different color -->
- <!-- color is calculated according to current position() -->
- <xsl:template name="calc_color">
- <xsl:param name="rel_position" select="number(0)"/>
- <xsl:variable name="color_lightnesses" select="'F0D0B09070503010E0C0A08060402000'"/>
- <xsl:text>#</xsl:text>
- <!-- red part -->
- <xsl:choose>
- <xsl:when test="($rel_position mod 3) = 0">F0</xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
- </xsl:otherwise>
- </xsl:choose>
- <!-- blue part -->
- <xsl:choose>
- <xsl:when test="($rel_position mod 3) = 1">F0</xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
- </xsl:otherwise>
- </xsl:choose>
- <!-- green part -->
- <xsl:choose>
- <xsl:when test="($rel_position mod 3) = 2">F0</xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:template>
- </xsl:stylesheet>